package com.haifeng.jupiter.gateway.filter;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.haifeng.jupiter.gateway.constant.Constants;
import com.haifeng.jupiter.gateway.dto.PermissionDto;
import com.haifeng.jupiter.gateway.dto.UserDto;
import com.haifeng.jupiter.gateway.exception.BusinessException;
import com.haifeng.jupiter.gateway.properties.AuthProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * <p>
 *  Token校验器
 * </p>
 *
 * @author: Haifeng
 * @date: 2020-08-11
 */
@Order(3)
@Component
public class TokenFilter extends AbstractGatewayFilterFactory {

    private AntPathMatcher antPathMatcher = new AntPathMatcher();
    private final AuthProperties authProperties;
    private final StringRedisTemplate stringRedisTemplate;

    public TokenFilter(AuthProperties authProperties, StringRedisTemplate stringRedisTemplate) {
        this.authProperties = authProperties;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public GatewayFilter apply(Object config) {

        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            // 如果不需要鉴权，则跳过
            Iterator<String> iterator = authProperties.getPassAuth().iterator();
            while (iterator.hasNext()){
                if (antPathMatcher.match(iterator.next(),path)){
                    return chain.filter(exchange);
                }
            }
            // 检查头信息是否包含Authorization的Token信息
            if (!request.getHeaders().containsKey(HttpHeaders.AUTHORIZATION)){
                throw new BusinessException(HttpStatus.HTTP_UNAUTHORIZED, Constants.NEED_TOKEN);
            }
            String token = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
            // 解析Token
            Claims claims;
            try {
                claims = Jwts.parser().setSigningKey(Constants.JWT_SIGN_KEY.getBytes(CharsetUtil.UTF_8))
                        .parseClaimsJws(token).getBody();
            } catch (Exception e) {
                if (e instanceof ExpiredJwtException){
                    // 令牌过期，使用refreshToken进行刷新
                    throw new BusinessException(Constants.TOKEN_EXPIRED_CODE, Constants.TOKEN_EXPIRED);
                }else {
                    throw new BusinessException(HttpStatus.HTTP_UNAUTHORIZED, Constants.TOKEN_ERROR);
                }
            }
            String json = (String)claims.get("user_name");
            // 将User信息放入请求头X-User，供微服务使用
            ServerHttpRequest newRequest = request.mutate()
                    .header(Constants.USER_INFO, json.replaceAll("\n","")).build();
            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            // 使用JWT自包含的信息验证，需要退出登录后生效
            UserDto userDto = JSONUtil.toBean(json, UserDto.class);
            Integer isAdmin = 1;
            if (isAdmin.equals(userDto.getSuperAdmin())){
                // 后台超级管理员直接跳过
                return chain.filter(newExchange);
            }
            if (authProperties.getUseRedis()){
                // 使用Redis中的信息验证，权限实时生效
                String id = userDto.getId();
                String userJson = stringRedisTemplate.opsForValue().get(id);
                if (StrUtil.isBlank(userJson)){
                    throw new BusinessException(HttpStatus.HTTP_FORBIDDEN, Constants.HAS_NO_PERMISSION);
                }
                userDto = JSONUtil.toBean(userJson, UserDto.class);
            }
            // 取出权限校验
            Set<PermissionDto> permissionList = userDto.getPermissionList();
            Boolean hasPermission = false;
            String realPath = getPath(path);
            if (CollUtil.isNotEmpty(permissionList)){
                for (PermissionDto permissionDto : permissionList) {
                    if (antPathMatcher.match(permissionDto.getPermissionUrl(), realPath)) {
                        hasPermission = true;
                        break;
                    }
                }
            }
            // 如果没有权限则抛出403异常
            if (!hasPermission){
                throw new BusinessException(HttpStatus.HTTP_FORBIDDEN, Constants.HAS_NO_PERMISSION);
            }
            return chain.filter(newExchange);
        };
    }

    /**
     * 获取真实路径
     * @param path
     * @return
     */
    public String getPath(String path){
        List<String> servicePrefix = authProperties.getServicePrefix();
        for (int i = 0; i < servicePrefix.size(); i++) {
            if (path.startsWith(servicePrefix.get(i))){
                path = path.replace(servicePrefix.get(i),"");
                break;
            }
        }
        return path;
    }
}
